<?php
/**
 * Guarana 1.0
 * http://code.google.com/p/guarana
 * Guarana is a light-weight PHP 4 and PHP 5 compatible framework that provides
 * a small development layer, without getting in the way of the programmer. It's PHP to go!
 *
 * Corey Culler <lov3cannon@gmail.com>
 * 
 * ============================================================================
 * 
 * Guarana configurations are used to control how Guarana
 * behaves. The core configurations below must always be set.
 * Additionaly, users can set their own configurations and
 * retrieve their value by calling the get_config('config_name') function.
 *
 * To learn more about each configuration, download the PDF User Guide
 * http://code.google.com/p/guarana/downloads/list
 */


// ===========================================================
// CORE CONFIGURATIONS
// ===========================================================
// Base URL
$arrConfig['base_url']					= 'http://example.com';

// Default Controller / Method
$arrConfig['default_controller']        = 'welcome';
$arrConfig['default_method']            = 'index';

// Autoloading
$arrConfig['auto_library']				= array();
$arrConfig['auto_model']				= array();
$arrConfig['auto_controller']			= array();

// Error Pages
$arrConfig['404_view']                  = 'errors/404';
$arrConfig['error_view']                = 'errors/error';

// ===========================================================
// CUSTOM CONFIGURATIONS
// Add your own configurations here
// ===========================================================
// Example: $arrConfig['thumbnail_size'] = 80;


/**
 * How PHP handles errors. Its a good idea to set this value to
 * 0 when in a live enviroment. E_ALL is the default.
 */
error_reporting(E_ALL ^ E_NOTICE);

//Turn this off if you want PHP to not show errors (recommended for production)
ini_set('display_errors', 1);


/**
 * Constants used to set URL locations
 * Don't dick with these.
 */
define('GUARANA_ROOT', str_replace('\\', '/', realpath('guarana')) . '/');
define('GUARANA_CONTROLLERS', 'app/controllers/');
define('GUARANA_MODELS', 'app/models/');
define('GUARANA_SOURCES', 'app/sources/');
define('GUARANA_VIEWS', 'app/views/');
define('GUARANA_LIBRARIES', 'libraries/');
define('GUARANA_EXT', '.php');
define('GUARANA_VERSION', '1.0'); // Dont change me


/**
 * Loads initial configurations into static array and provides a way to
 * retrieve any configuration by passing its key.
 *
 * The last two parameters are only used during Guarana init.
 *
 * @param string $strKey The configuration you want a value for
 * @param array $arrConfigs Configurations to be loaded into the local static array
 * @param boolean $boolLoad Loads $arrConfigs into local static array if true
 *
 * @return mixed Configuration value associated with $strKey
 */
function get_config($strKey, $arrConfigs = array(), $boolLoad = false)
{
    static $arrConfig;
    if($boolLoad)
        $arrConfig = $arrConfigs;
    else
    {
        if(isset($arrConfig[$strKey]))
            return $arrConfig[$strKey];
        else
            return false;
    }
}


/**
 * Used to set view data from anywhere in your application.
 * When the next view() function is called, the set data will
 * be loaded into the view, and the data cleared.
 *
 * @param string $strKey The variable name to use in view
 * @param mixed $mixValue The value assigned to $strKey
 * @param boolean $boolReturn Returns and clears view data if true
 * 
 * @return array Collection of view data, if any
 */
function view_data($strKey, $mixValue, $boolReturn = false)
{
    static $arrViewData;
    if($boolReturn)
    {
        if(is_array($arrViewData))
        {
            $tmpData = $arrViewData;
            $arrViewData = array();
            return $tmpData;
        }
        return array();
    }
    $arrViewData[$strKey] = $mixValue;
}


/**
 * Loads view data into view file and outputs view file to the browser.
 *
 * @param string $strFile Name of view file (without .php extension) to be loaded
 * @param array $arrData Array of view data to be loaded into view
 */
function view($strFile, $arrData = array())
{
    $arrData += view_data('', '', true);
    load('view', $strFile, false, false, $arrData);
}


/**
 * Loads Libraries, Models and Controllers.
 * Only calls functions or objects if the $boolCall param is
 * true. This is helpful when doing includes for extending
 * other classes
 *
 * @param string $strType The type of file to load (ie: 'Controller', 'Model' or 'View')
 * @param string $strName The name of the file and class or function to call
 * @param boolean $boolCall Calls function and/or creates new instance of $strName if true
 *
 * @return object Returns and instance of $strName if a class is found
 */
function load($strType, $strName, $boolCallFunc = true, $boolCallClass = true, $arrData = array())
{
    $strType = strtolower($strType);
    $arrType = array
    (
        'controller' => GUARANA_CONTROLLERS,
        'model' => GUARANA_MODELS,
        'view' => GUARANA_VIEWS,
        'library' => GUARANA_LIBRARIES
    );
    $strFile = GUARANA_ROOT . $arrType[$strType] . guarana_format($strType, $strName) . GUARANA_EXT;
    if(file_exists($strFile))
    {
        // If loading view, and view has data, load the data
        if($strType == 'view' && !empty($arrData))
		{
            foreach($arrData as $strKey => $mixValue)
                $$strKey = $mixValue;
		}
                
        // Require once is faster then checking a static array for loaded files
        require_once($strFile);
		
		// If controller, check if a class or default method exists
		// If not, throw error (controllers require a class or function)
		if($strType == 'controller')
		{
			$loadError = true;
			if(function_exists(get_config('default_method')))
				$loadError = false;
			if(class_exists($strName))
				$loadError = false;
			if($loadError)
				guarana_error(ucfirst($strType) . ' doesn\'t contain a function or class: ' . $strFile);
		}
                
        // If name contains segments, we must be using subdirectories. 
		// Get the last segment to determine file
        if(preg_match('@/@', $strName))
        {
            $arrBits = explode('/', $strName);
            $strName = $arrBits[count($arrBits) - 1];
        }
        
		// If we should call a function and a function with the same name as the file
		// exists, then call it. Or, if we are loading a controller and a function with the
		// method name exists, then call it.
        $strMethod = uri('method');
		if($boolCallFunc)
		{
			if(function_exists($strName))
				$strName();
			elseif($strType == 'controller' && function_exists($strMethod))
				$strMethod();
		}
		
		// If we should call a class and a class with the same name as the file
		// exists, then instantiate it.
		if($boolCallClass && class_exists($strName))
		{
			$objTmp = new $strName();
			
			// If we're  instantiation a controller, attempt to call its method
			// If no method is found, show 404
			if($strType == 'controller')
			{
				if(method_exists($objTmp, $strMethod))
					$objTmp->$strMethod();
				else
					guarana_error('404');
			}
			return $objTmp;
		}
        
        // Catch
        return null;
    }
    else
        guarana_error(ucfirst($strType). ' file doesn\'t exist: ' . $strFile);
}


/**
 * Get/load the value of a URL segment.
 *
 * @param string $strSegment The segment name you want a value for
 * @param array $arrSegments Array of segments to be loaded into static $arrUri variable
 * @param boolean $boolLoadSegments If true, loads $arrSegments into static $arrUri variable
 *
 * @return mixed The value of $strSegment if exists, else false
 */
function uri($strSegment, $arrSegments = array(), $boolLoadSegments = false)
{
    static $arrUri;
    if($boolLoadSegments)
        $arrUri = $arrSegments; // Load into static
    else
    {
        if(is_int($strSegment))
            return (isset($arrUri['params'][$strSegment])) ? $arrUri['params'][$strSegment] : false;
        else
            return (isset($arrUri[$strSegment])) ? $arrUri[$strSegment] : false;
    }
}


/**
 * Returns the full URL path to the given source file
 *
 * @param string $strSourceFile Source file name (ex: 'style.css')
 * @return string The full URL path to given source file
 */
function source($strSourceFile)
{
    return uri('base') . '/caffeine/' . GUARANA_SOURCES . trim($strSourceFile, '/');
}


/**
 * Returns the full URL path to the given controller, method or param path.
 *
 * @param string $strPath The path you want a full URL to
 */
function anchor($strPath)
{
    return uri('base') . trim($strPath, '/');
}


/**
 * Handles errors generated by Guarana.
 *
 * @param string $strMessage the error message written to browser and log file.
 */
function guarana_error($strMessage)
{
    switch($strMessage)
    {
        case '404':
			$view404 = get_config('404_view');
            if(file_exists(GUARANA_ROOT . GUARANA_VIEWS . guarana_format('view', $view404) . GUARANA_EXT))
            {
                view($view404);
                break;
            }
            else
                die('404: Page Not Found');
        default:
			$errorView = get_config('error_view');
            if(file_exists(GUARANA_ROOT . GUARANA_VIEWS . guarana_format('view', $errorView) . GUARANA_EXT))
            {
				view_data('error', $strMessage);
                view($errorView);
                break;
            }
            else
                die('Application Error: ' . $strMessage);
    }
    die(); // Catch
}


/**
 * Format all loaded files to use the Guarana file standard
 * All files must begin with their type and a period.
 */
function guarana_format($strType, $strFile)
{
    // If the file is in a dir, get file name out of string
    // then prepend type format and rebuild the string
    if(preg_match('@/@', $strFile))
    {
        $arrBits = explode('/', $strFile);
        $intLast = count($arrBits) - 1;
        $arrBits[$intLast] = $strType . '.' . $arrBits[$intLast];
        $strFile = implode('/', $arrBits);
    }
    // Else file only, just prepend type format
    else
        $strFile = $strType . '.' . $strFile;
    
    return $strFile;
}


/**
 * Used to autoload Controller, Models or Libraries at run time.
 * If either file contains a function with the same name as the file,
 * the function is called.
 *
 * This provides a way to do pre-processing.
 */
function guarana_autoload()
{
	$arrTypes = array('library', 'model', 'controller');
	foreach($arrTypes as $type)
	{
		$arrType = get_config('auto_' . $type);
		if(is_array($arrType) && !empty($arrType))
		{
			foreach($arrType as $strFile);
				load($type, $strFile, ($type == 'controller') ? false : true, false);
		}
	}
}


/**
 * Used to reroute the current URL based on route configurations.
 * Routes are determined by user set or predefined regular expressions.
 *
 * @param string $strURL
 * @return string
 */
function guarana_reroute($strURL)
{
    $arrRoutes = get_config('route');
    if($arrRoutes)
    {
        foreach($arrRoutes as $strRegex => $strDest)
        {
            $strRegex = '^' . $strRegex; // start with...
            $strRegex = str_replace(':num', '[0-9]{1,}', $strRegex);
            $strRegex = str_replace(':abc', '[A-Za-z]{1,}$', $strRegex);
            $strRegex = str_replace(':any', '[A-Za-z0-9-_/]{1,}$', $strRegex);
            $strRegex = $strRegex . '$'; // end with...
            if(preg_match_all('@' . $strRegex . '@', $strURL, $arrMatches, PREG_SET_ORDER))
            {
                $intCount = 0;
                foreach($arrMatches[0] as $strMatch)
                {
                    $strDest = str_replace('$' . $intCount, $strMatch, $strDest);
                    $intCount++;
                }
                return $strDest;
            }
        }
    }
    return $strURL;
}


/**
 * Parses segments out of the URL to determine which controller and methods
 * to call along with parameters. If a valid segment isn't found, a default from
 * configurations is used.
 */
function guarana_router()
{
	// Parse URL
    
	$_SERVER['PATH_INFO'] = (isset($_SERVER['ORIG_PATH_INFO'])) ? $_SERVER['ORIG_PATH_INFO'] : $_SERVER['PATH_INFO']; 
	
    // Cleanup slashes
    $_SERVER['PATH_INFO'] = trim($_SERVER['PATH_INFO'], '/');
	$strSegments = $_SERVER['PATH_INFO'];
    //If for some reason PATH_INFO isnt usable, default to manually building URL
    if($strSegments == null || $strSegments == '') {

        $strCurrent = (@$_SERVER['HTTPS'] == 'on') ? 'https' : 'http' . '://';
        if($_SERVER['SERVER_PORT'] != 80)
            $strCurrent .= $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI'];
        else
            $strCurrent .= $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];

        // Cleanup slashes
        $strCurrent = rtrim($strCurrent, '/');
        $strBase = rtrim(get_config('base_url'), '/');
        $strSegments = trim(str_replace($strBase, '', $strCurrent), '/'); // Trim before adding slash so we dont get doubles
        //$strSegments = rtrim('=', $strSegments);
        if(count($_GET) > 0) {
            $seperator = '';
            $equalsign = '=';
            if(count($_GET) > 1) {
                $seperator = '&';
            }
            $baseget = '?';
            foreach($_GET as $key => $value) {
                if($value == null) {
                    $equalsign = '';
                }
                $baseget .= "$key$equalsign$value$seperator";
            }
            
            $baseget = rtrim($baseget, '&');
            $strSegments = rtrim($strSegments, '=');

            $strSegments = str_replace($baseget, '', $strSegments);
        }

    }
    $strSegments = guarana_reroute($strSegments); // Handle custom routes
	
	// Check for valid URL characters
    if(!preg_match('/[^A-Za-z0-9\:\/\.\-\_\#]/i', $strSegments))
    {
		// Break URL into URI segments
        $arrUrlBits = (!empty($strSegments)) ? explode('/', $strSegments) : array();
		
        // Set segment defaults
        $arrSegments['controller'] = get_config('default_controller');
        $arrSegments['method'] = get_config('default_method');
        $arrSegments['params'] = array();
        
        // If URL segments exist, attempt to overwrite defaults
        if(count($arrUrlBits) > 0)
        {
            $boolResult = false;
            $arrPop = array();
            $strPath = GUARANA_ROOT . GUARANA_CONTROLLERS;
            while($arrUrlBits)
            {
                $strTmp = implode('/', $arrUrlBits);
                $arrPop[] = array_pop($arrUrlBits);
                
                // Test if last segment is controller
                if(file_exists($strPath . guarana_format('controller', $strTmp) . GUARANA_EXT))
                {
                    $arrSegments['controller'] = $strTmp;
                    $boolResult = true;
                }
    
                // Else, test if last segment is dir and default controller exists within it
                elseif(file_exists($strPath . $strTmp . '/' . guarana_format('controller', $arrSegments['controller']) . GUARANA_EXT))
                {
                    $arrSegments['controller'] = $strTmp . '/' . $arrSegments['controller'];
                    $boolResult = true;
                }
                
                // If we found a controller, get method and param segments
                // and break from loop
                if($boolResult)
                {
                    $arrPop = array_reverse($arrPop);
                    if(isset($arrPop[1])) $arrSegments['method'] = $arrPop[1];
                    if(isset($arrPop[2])) $arrSegments['params'] = array_slice($arrPop, 2);
                    break;
                }
            }
			
			// If there where segments, but no files matched, show 404
			if(!$boolResult) guarana_error('404');
        }
		
		// Load segments into static URI function
		$arrSegments['base'] = $strBase;
		$arrSegments['current'] = $strCurrent;
        uri('', $arrSegments, true);
		
		// If we got this far, we're ready to load the controller
	    load('controller', $arrSegments['controller']);
    }
    else
        guarana_error('The URL you entered contains illegal characters.');
}


/**
 * Starting point for Guarana. Calls guarana_router to determine URI segments
 * then loads controller and method accordingly.
 *
 * @param array $arrConfig User-defined configurations
 */
function guarana_init($arrConfig)
{
    if($arrConfig['base_url'] != 'http://' && $arrConfig['base_url'] != '')
    {
        get_config('', $arrConfig, true);
        guarana_autoload();
        guarana_router();
    }
    else
        guarana_error('You haven\'t set the base URL configuration in your index.php file.');
}
//Used for profiling
function getMicrotime(){
    list($useg,$seg)=explode(' ',microtime());
    return ((float)$useg+(float)$seg);
}
global $profbegin;
function profile_begin() {
    global $profbegin;
    $profbegin = getMicrotime();
}

function profile_end() {
    global $profbegin;
    $begin = $profbegin;
    $end = getMicrotime();
    return $end-$begin;
}

function object_to_array($object)
{
  if(is_array($object) || is_object($object))
  {
    $array = array();
    foreach($object as $key => $value)
    {
      $array[$key] = object_to_array($value);
    }
    return $array;
  }
  return $object;
}

/**
 * Start Guarana
 */

profile_begin();

guarana_init($arrConfig);
?>